﻿using System;
using System.Diagnostics.Eventing;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Security;

// copied from Microsoft.SystemCenter.Advisor.Internal.dll

namespace QND.SCOMMM.Tracing
{
    internal class TraceHelper
    {
        private static TraceProvider traceProvider;
        private static Guid[] messageGuids;
        static TraceHelper()
        {
            TraceHelper.traceProvider = new TraceProvider("QND.SCOMMM.Provider", new Guid("{E4A69BBA-7AC8-40E1-832A-4B6996291D38}"), true);
            Guid[] guid = new Guid[] { new Guid("{E6104BC1-2418-499B-910C-A2EEEFBD2FDC}"), new Guid("{FCFF27BF-6BEE-4151-8BD4-7EEB55A29527}") };
            TraceHelper.messageGuids = guid;
        }

        public TraceHelper()
        {
        }

        internal static uint WriteEventString(TraceLevel level, TraceFlags flags, ushort messageGuid, ushort messageNumber, string message)
        {
            uint result = 99;
            Debug.Print(String.Format("QND-TraceHelper.WriteEventString level:{0} flags:{1} guidIdx:{2} MsgNo:{3} - {4}",level, flags, messageGuid, messageNumber, message));
            if (messageGuid >= TraceHelper.messageGuids.Length) { return 99; }
            if (traceProvider.IsEnabled && traceProvider.Level >= (byte)level && (traceProvider.Flags & (uint)flags) != 0)
            {
                Debug.Print("QND-TraceHelper.WriteEventString Message is Enabled and at the proper level and flags - writing etw");
                result= TraceHelper.traceProvider.WriteEventString(TraceHelper.messageGuids[messageGuid], messageNumber, message);
                Debug.Print(String.Format("QND-TraceHelper.WriteEventString Result:{0}",result));
            }
            return result;
        }
        internal enum TraceLevel : byte
        {
            Fatal = 1,
            Error = 2,
            Warning = 3,
            Information = 4,
            Performance = 5,
            Verbose = 6
        }
        internal enum TraceFlags : uint
        {
            Error = 0x1,
            Warning = 0x10,
            Information = 0x100,
            Performance = 0x1000,
            Verbose = 0x10000,
            Debug = 0x10000
        }
    }

    //[Guid("748004CA-4959-409a-887C-6546438CF48E")]  //this one seems to be fixed 
    internal class TraceProvider
    {
        private const int maxTraceMessageSize = 8000;

        internal UnsafeNativeMethods.WMIDPREQUEST etwCallback;

        private bool useSequenceNumbers;

        private ulong registrationHandle;

        private ulong traceHandle;

        private byte level;

        private uint flags;

        private bool enabled;

        private Guid controlGuid;

        private string applicationName;

        private TraceProvider.GetTraceLoggerHandle getTraceLoggerHandle;

        private TraceProvider.GetTraceEnableFlags getTraceEnableFlags;

        private TraceProvider.GetTraceEnableLevel getTraceEnableLevel;

        internal string ApplicationName
        {
            get
            {
                return this.applicationName;
            }
        }

        internal int Flags
        {
            get
            {
                return (int)this.flags;
            }
        }

        internal bool IsEnabled
        {
            get
            {
                return this.enabled;
            }
        }

        internal byte Level
        {
            get
            {
                return this.level;
            }
        }

        internal TraceProvider(string applicationName, Guid controlGuid, bool useSequenceNumbers)
        {
            this.applicationName = applicationName;
            this.controlGuid = controlGuid;
            this.useSequenceNumbers = useSequenceNumbers;
            IntPtr moduleHandle = UnsafeNativeMethods.GetModuleHandle("advapi32.dll");
            IntPtr procAddress = UnsafeNativeMethods.GetProcAddress(moduleHandle, "GetTraceLoggerHandle");
            this.getTraceLoggerHandle = (TraceProvider.GetTraceLoggerHandle)Marshal.GetDelegateForFunctionPointer(procAddress, typeof(TraceProvider.GetTraceLoggerHandle));
            procAddress = UnsafeNativeMethods.GetProcAddress(moduleHandle, "GetTraceEnableFlags");
            this.getTraceEnableFlags = (TraceProvider.GetTraceEnableFlags)Marshal.GetDelegateForFunctionPointer(procAddress, typeof(TraceProvider.GetTraceEnableFlags));
            procAddress = UnsafeNativeMethods.GetProcAddress(moduleHandle, "GetTraceEnableLevel");
            this.getTraceEnableLevel = (TraceProvider.GetTraceEnableLevel)Marshal.GetDelegateForFunctionPointer(procAddress, typeof(TraceProvider.GetTraceEnableLevel));
            this.Register();
        }

        ~TraceProvider()
        {
            UnsafeNativeMethods.UnregisterTraceGuids(this.registrationHandle);
            GC.KeepAlive(this.etwCallback);
        }


        internal unsafe uint WriteEventString(Guid messageGuid, ushort messageNumber, string message)
        {
            uint result=0;
            // prepare the relevant structres
            UnsafeNativeMethods.TRACE_MESSAGE_FLAGS tRACEMESSAGEFLAG = UnsafeNativeMethods.TRACE_MESSAGE_FLAGS.TRACE_MESSAGE_GUID | UnsafeNativeMethods.TRACE_MESSAGE_FLAGS.TRACE_MESSAGE_TIMESTAMP | UnsafeNativeMethods.TRACE_MESSAGE_FLAGS.TRACE_MESSAGE_SYSTEMINFO;
            if (this.useSequenceNumbers)
            {
                tRACEMESSAGEFLAG = tRACEMESSAGEFLAG | UnsafeNativeMethods.TRACE_MESSAGE_FLAGS.TRACE_MESSAGE_SEQUENCE;
            }
            int bufferSize = sizeof(UnsafeNativeMethods.VALIST_FIELD) * 2; //*2 due to the terminating element
            byte* buffer = stackalloc byte[bufferSize];  //used stackalloc for performance http://stackoverflow.com/questions/785226/practical-use-of-stackalloc-keyword 
            // parameter check on input string
            if(String.IsNullOrEmpty(message)) {message = @"NULL";}
            if((message.Length * sizeof(char)) > (maxTraceMessageSize)) {message = message.Substring(0,maxTraceMessageSize/(sizeof (char)));}

            // set the last eleemnt to null
            UnsafeNativeMethods.VALIST_FIELD* vALISTFIELDPointer = (UnsafeNativeMethods.VALIST_FIELD*)(buffer + sizeof(UnsafeNativeMethods.VALIST_FIELD));
            (*vALISTFIELDPointer).DataPointer = null;
            (*vALISTFIELDPointer).DataLength = 0;
            // set the string and trace it
            vALISTFIELDPointer = (UnsafeNativeMethods.VALIST_FIELD*)buffer;
            (*vALISTFIELDPointer).DataLength = (uint)(message.Length * sizeof(char));

            // now I must fix the element so that the GC cannot steo in and move the variables
            /* char* offset = str;
            * if (offset != null)
            * {
            *     offset = offset + RuntimeHelpers.OffsetToStringData;
            * }
            */
            fixed(char *msg = message) {
                (*vALISTFIELDPointer).DataPointer = msg;
                result=UnsafeNativeMethods.TraceMessageVa(this.traceHandle, (uint)tRACEMESSAGEFLAG, &messageGuid, messageNumber, buffer);
            }
            return result;
        }

        private unsafe uint MyCallback(UnsafeNativeMethods.WMIDPREQUESTCODE requestCode, IntPtr context, uint* bufferSize, byte* byteBuffer)
        {
            UnsafeNativeMethods.WNODE_HEADER* wNODEHEADERPointer = (UnsafeNativeMethods.WNODE_HEADER*)byteBuffer;
            switch (requestCode)
            {
                case UnsafeNativeMethods.WMIDPREQUESTCODE.WMI_ENABLE_EVENTS:
                    {
                        this.traceHandle = this.getTraceLoggerHandle(wNODEHEADERPointer);
                        this.flags = this.getTraceEnableFlags(this.traceHandle);
                        this.level = this.getTraceEnableLevel(this.traceHandle);
                        this.enabled = true;
                        break;
                    }
                case UnsafeNativeMethods.WMIDPREQUESTCODE.WMI_DISABLE_EVENTS:
                    {
                        this.enabled = false;
                        this.traceHandle = (ulong)0;
                        this.level = 0;
                        this.flags = 0;
                        break;
                    }
                default:
                    {
                        this.enabled = false;
                        this.traceHandle = (ulong)0;
                        break;
                    }
            }
            return (uint)0;
        }

        private uint Register()
        {
            unsafe
            {
                this.etwCallback = new UnsafeNativeMethods.WMIDPREQUEST(this.MyCallback);
                // needed as a place holder only didn't find a way to set it to null even if the doc says it is possible http://msdn.microsoft.com/en-us/library/windows/desktop/aa364105(v=vs.85).aspx
                UnsafeNativeMethods.TRACE_GUID_REGISTRATION zero = new UnsafeNativeMethods.TRACE_GUID_REGISTRATION();
                Guid guid = new Guid("{5BC62AA8-0F6E-490E-B9FF-6C45F527D7C4}");
                zero.Guid = &guid;
                zero.RegHandle = IntPtr.Zero;
                return UnsafeNativeMethods.RegisterTraceGuids(this.etwCallback, null, ref this.controlGuid, 1, ref zero, null, null, out this.registrationHandle);
            }
        }

        [SuppressUnmanagedCodeSecurity]
        private delegate uint GetTraceEnableFlags(ulong TraceHandle);

        [SuppressUnmanagedCodeSecurity]
        private delegate byte GetTraceEnableLevel(ulong TraceHandle);

        [SuppressUnmanagedCodeSecurity]
        private unsafe delegate ulong GetTraceLoggerHandle(UnsafeNativeMethods.WNODE_HEADER* Buffer);


    }

    internal sealed class UnsafeNativeMethods
    {
        internal const int TRACE_MESSAGE_MAXIMUM_SIZE = 8192;

        internal const uint ERROR_INVALID_PARAMETER = 87;

        internal UnsafeNativeMethods()
        {
        }

        [DllImport("kernel32.dll", CharSet = CharSet.Unicode, EntryPoint = "GetModuleHandleW", ExactSpelling = false)]
        [SuppressUnmanagedCodeSecurity]
        internal static extern IntPtr GetModuleHandle([In] string lpModuleName);

        [DllImport("kernel32.dll", CharSet = CharSet.Ansi, ExactSpelling = false)]
        [SuppressUnmanagedCodeSecurity]
        internal static extern IntPtr GetProcAddress([In] IntPtr hModule, [In] string lpProcName);

        [DllImport("advapi32.dll", CharSet = CharSet.Unicode, EntryPoint = "RegisterTraceGuidsW", ExactSpelling = false)]
        [SuppressUnmanagedCodeSecurity]
        internal static extern unsafe uint RegisterTraceGuids([In] UnsafeNativeMethods.WMIDPREQUEST RequestAddress, [In] void* RequestContext, [In] ref Guid ControlGuid, [In] uint GuidCount, ref UnsafeNativeMethods.TRACE_GUID_REGISTRATION TraceGuidReg, [In] string MofImagePath, [In] string MofResourceName, out ulong RegistrationHandle);

        [DllImport("advapi32.dll", CharSet = CharSet.None, ExactSpelling = true)]
        [SuppressUnmanagedCodeSecurity]
        internal static extern unsafe uint TraceEvent(ulong TraceHandle, UnsafeNativeMethods.EVENT_TRACE_BUFFER* EventTrace);

        [DllImport("advapi32.dll", CharSet = CharSet.None, ExactSpelling = true)]
        [SuppressUnmanagedCodeSecurity]
        internal static extern unsafe uint TraceMessageVa(ulong TraceHandle, uint MessageFlags, Guid* MessageGuid, ushort MessageNumber, void* MessageArgList);

        [DllImport("advapi32.dll", CharSet = CharSet.None, ExactSpelling = true)]
        [SuppressUnmanagedCodeSecurity]
        internal static extern int UnregisterTraceGuids(ulong RegistrationHandle);

        [StructLayout(LayoutKind.Explicit)]
        internal struct EVENT_TRACE_BUFFER
        {
            [FieldOffset(0)]
            internal uint BufferSize;

            [FieldOffset(4)]
            internal uint ProviderId;

            [FieldOffset(8)]
            internal uint ThreadId;

            [FieldOffset(12)]
            internal uint ProcessId;

            [FieldOffset(16)]
            internal long TimeStamp;

            [FieldOffset(24)]
            internal Guid Guid;

            [FieldOffset(40)]
            internal uint ClientContext;

            [FieldOffset(44)]
            internal uint Flags;

            [FieldOffset(48)]
            internal UnsafeNativeMethods.MOF_FIELD UserData;
        }

        [StructLayout(LayoutKind.Explicit)]
        internal struct MOF_FIELD
        {
            [FieldOffset(0)]
            internal ulong ZeroInit;

            [FieldOffset(0)]
            internal unsafe void* DataPointer;

            [FieldOffset(8)]
            internal uint DataLength;

            [FieldOffset(12)]
            internal uint DataType;
        }

        internal struct TRACE_GUID_REGISTRATION
        {
            internal unsafe Guid* Guid;

            internal IntPtr RegHandle;
        }

        [Flags]
        internal enum TRACE_MESSAGE_FLAGS : uint
        {
            TRACE_MESSAGE_SEQUENCE = 1,
            TRACE_MESSAGE_GUID = 2,
            TRACE_MESSAGE_COMPONENTID = 4,
            TRACE_MESSAGE_TIMESTAMP = 8,
            TRACE_MESSAGE_PERFORMANCE_TIMESTAMP = 16,
            TRACE_MESSAGE_SYSTEMINFO = 32
        }

        //[StructLayout(LayoutKind.Sequential)]
        internal struct VALIST_FIELD
        {
            internal unsafe void* DataPointer;

            internal uint DataLength;
        }

        internal unsafe delegate uint WMIDPREQUEST(UnsafeNativeMethods.WMIDPREQUESTCODE RequestCode, IntPtr RequestContext, uint* BufferSize, byte* Buffer);

        internal enum WMIDPREQUESTCODE : uint
        {
            WMI_ENABLE_EVENTS = 4,
            WMI_DISABLE_EVENTS = 5
        }

        internal enum WNODE_FLAGS : uint
        {
            WNODE_FLAG_TRACED_GUID = 131072,
            WNODE_FLAG_USE_GUID_PTR = 524288,
            WNODE_FLAG_USE_MOF_PTR = 1048576
        }

        [StructLayout(LayoutKind.Explicit)]
        internal struct WNODE_HEADER
        {
            [FieldOffset(0)]
            internal uint BufferSize;

            [FieldOffset(4)]
            internal uint ProviderId;

            [FieldOffset(8)]
            internal IntPtr HistoricalContext;

            [FieldOffset(16)]
            internal long TimeStamp;

            [FieldOffset(24)]
            internal Guid Guid;

            [FieldOffset(40)]
            internal uint ClientContext;

            [FieldOffset(44)]
            internal uint Flags;
        }
    }
}